/**
 * 获取元素的边界矩形，若元素无效则返回0
 * @param element - 要获取边界的 DOM 元素
 * @returns 如果元素有效，返回 DOMRect 对象；如果元素无效，返回数字 0
 */
export const getBoundingClientRect = (element: Element): DOMRect | number => {
  if (!element || !element.getBoundingClientRect) {
    return 0
  }
  return element.getBoundingClientRect()
}

/**
 * 去除字符串两端的空白字符
 * @param string
 * @returns {string} 返回去除两端空白字符后的字符串
 */
const trim = (string: string) => {
  return (string || '').replaceAll(/^\s+|\s+$/g, '')
}

/**
 * 判断元素是否具有指定的类名
 * @param el
 * @param cls
 * @returns {boolean} 如果元素具有指定的类名，返回 true；否则返回 false
 */
export const hasClass = (el: Element, cls: string) => {
  if (!el || !cls) return false
  if (cls.includes(' ')) throw new Error('className should not contain space.')
  return el.classList ? el.classList.contains(cls) : ` ${el.className} `.includes(` ${cls} `)
}

/**
 * 为元素添加类名
 * @param el - 目标 DOM 元素
 * @param cls - 要添加的类名字符串
 */
export const addClass = (el: Element, cls: string) => {
  if (!el) return
  let curClass = el.className
  const classes = (cls || '').split(' ')

  for (let i = 0, j = classes.length; i < j; i++) {
    const clsName = classes[i]
    if (!clsName) continue

    if (el.classList) {
      el.classList.add(clsName)
    } else if (!hasClass(el, clsName)) {
      curClass += ` ${clsName}`
    }
  }
  if (!el.classList) {
    el.className = curClass
  }
}

/**
 * 为元素移除类名
 * @param el - 目标 DOM 元素
 * @param cls - 要移除的类名字符串
 */
export const removeClass = (el: Element, cls: string) => {
  if (!el || !cls) return
  const classes = cls.split(' ')
  let curClass = ` ${el.className} `

  for (let i = 0, j = classes.length; i < j; i++) {
    const clsName = classes[i]
    if (!clsName) continue

    if (el.classList) {
      el.classList.remove(clsName)
    } else if (hasClass(el, clsName)) {
      curClass = curClass.replace(` ${clsName} `, ' ')
    }
  }
  if (!el.classList) {
    el.className = trim(curClass)
  }
}

/**
 * 获取元素相对于视口的偏移量
 * @param element
 * @returns {object} 返回元素相对于视口的偏移量
 */
export const getViewportOffset = (element: Element) => {
  const doc = document.documentElement

  const docScrollLeft = doc.scrollLeft
  const docScrollTop = doc.scrollTop
  const docClientLeft = doc.clientLeft
  const docClientTop = doc.clientTop

  const pageXOffset = window.pageXOffset
  const pageYOffset = window.pageYOffset

  const box = getBoundingClientRect(element)

  const { height: rectHeight, left: retLeft, top: rectTop, width: rectWidth } = box as DOMRect

  const scrollLeft = (pageXOffset || docScrollLeft) - (docClientLeft || 0)
  const scrollTop = (pageYOffset || docScrollTop) - (docClientTop || 0)
  const offsetLeft = retLeft + pageXOffset
  const offsetTop = rectTop + pageYOffset

  const left = offsetLeft - scrollLeft
  const top = offsetTop - scrollTop

  const clientWidth = window.document.documentElement.clientWidth
  const clientHeight = window.document.documentElement.clientHeight
  return {
    bottom: clientHeight - rectHeight - top,
    bottomIncludeBody: clientHeight - top,
    left,
    right: clientWidth - rectWidth - left,
    rightIncludeBody: clientWidth - left,
    top
  }
}

/**
 * 为元素添加事件监听器
 * @param element
 * @param event
 * @param handler
 */
export const on = (element: Document | Element | HTMLElement | Window, event: string, handler: EventListenerOrEventListenerObject): void => {
  if (element && event && handler) {
    element.addEventListener(event, handler, false)
  }
}

/**
 * 为元素移除事件监听器
 * @param element
 * @param event
 * @param handler
 */
export const off = (element: Document | Element | HTMLElement | Window, event: string, handler: EventListenerOrEventListenerObject): void => {
  if (element && event && handler) {
    element.removeEventListener(event, handler, false)
  }
}

/**
 * 为元素添加仅执行一次的事件监听器
 * @param el
 * @param event
 * @param fn
 */
export const once = (el: HTMLElement, event: string, fn: EventListener): void => {
  const listener = function (this: any, ...args: unknown[]) {
    if (fn) {
      fn.apply(this, args as [evt: Event])
    }
    off(el, event, listener)
  }
  on(el, event, listener)
}

/**
 * 使用 requestAnimationFrame 实现的节流函数
 * @param fn 要节流的函数
 * @returns 返回节流后的函数
 */
export const useRafThrottle = <T extends (...args: any[]) => void>(fn: T): T => {
  let locked = false
  // 创建一个包装函数，确保类型一致性
  const throttledFn = (...args: Parameters<T>): any => {
    if (locked) return
    locked = true
    window.requestAnimationFrame(() => {
      fn.apply(this, args) // 调用原始函数
      locked = false
    })
  }
  return throttledFn as unknown as T // 明确类型转换
}

/**
 * 设置css变量
 * @param prop 变量属性
 * @param val 变量值
 * @param dom 要设置的元素，默认为 document.documentElement
 */
export const setCssVar = (prop: string, val: any, dom = document.documentElement) => {
  dom.style.setProperty(prop, val)
}

/**
 * 删除css变量
 * @param prop 变量属性
 * @param dom 要设置的元素
 */
export const removeCssVar = (prop: string, dom = document.documentElement) => {
  dom.style.removeProperty(prop)
}
